#pragma once
#include <Arduino.h>
#include <Preferences.h>

#define MAX_CONFIG_ARRY 64 // 配置数组最长长度
// 获取 结构体中变量的字节偏移量
#define OFFSET(structure, member) ((int32_t) & ((structure *)0)->member)

/**
 * @brief 握手状态
 *
 */
enum EhandshakeStatus
{
    /*  关闭扫描
     *  1. 默认状态， 只有在关闭扫描状态下才能开启扫描， 在结束扫描前不能再次扫描
     */
    closeConnect = 0x00,

    /*  准备接收
     *  1. 在准备接收的状态下，才会会受到的数据进行握手判断，其他状态下并不会对握手包进行处理
     */
    readyConnect = 0x01,

    /*  开启扫描
     *  1. 开始扫描切换到此状态，值和 准备接收的状态是一样的
     */
    openConnect = 0x01,

    /*  收到广播
     *  1. 收到握手包的时候，切换到此状态，将发送握手包的设备加入设备列表中，并且向对方发送
     *     应答包 CMD_HANDSHAKE_ASK 对方收到应答包后，会添加节点列表中
     *  2. 率先收到握手包的设备会添加节点，然后发送 ASK 应答包，然后对方才会添加节点
     */
    receivedBroadcast = 0x02, // 收到广播

    /*  已经链接
     *  1. 收到握手包的应答包后，会切换到此状态，并将发送方添加到节点列表里面
     */
    hasConnected = 0x03, // 已经链接
};

/**
 * @brief 消息结构体
 *
 */
struct MESSAGE
{
    uint8_t head[2] = {0xFF, 0xFE}; // 帧头
    uint16_t sn;                    // SN 编号
    uint8_t cmd;                    // 指令
    uint8_t len;                    // 数据长度
    uint8_t data[64];               // 数据内容
    uint8_t sum;                    // 校验和
    uint8_t address[6];             // 发送信息节点地址 不会在ESPNOW发送中被封包

    MESSAGE() {}                                                       // 默认构造函数
    MESSAGE(const uint8_t *buf);                                       // 使用数组构造，用于数据接收阶段
    MESSAGE(uint16_t _sn, uint8_t _cmd, uint8_t _len = 0, ...);        // 用不定参数的指令数据 构造
    MESSAGE(uint16_t _sn, uint8_t _cmd, uint8_t _len, uint8_t *_data); // 用指令数据数组 构造

    uint8_t length() { return OFFSET(MESSAGE, data) + len + 1; } // 获取结构体实际长度
    uint8_t setSum();                                            // 计算校验和, 返回结果
    uint8_t *toArry();                                           // 将结构体转化成数组
};

#define MAX_PEERS 15   // espnow 能存储的最大节点列表
#define INFO_LENGTH 24 // 节点信息结构体长度

/**
 * @brief 节点信息结构体
 *
 * 结构体中, 只有 SN 和 address 两个数据会被保存到 nvs中, 因而, toArry 中也只是对这两部分数据进行保存
 */
struct peer_info
{
    uint16_t sn;        // 节点SN号
    uint8_t status;     // 节点状态 0 表示挂起状态 1 表示激活状态
    uint8_t pre_status; // 之前的节点状态 0 表示挂起状态 1 表示激活状态
    uint8_t failures;   // 记录链接该设备发送失败的次数 4次失败后 判定该设备离线
    uint8_t address[6]; // 节点地址   格式 { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }

    uint8_t *toArry(); // 将结构体转化成数组
};

/**
 * @brief 配置信息结构体
 *
 */
class CConfig : public Preferences
{

public:
    uint16_t sn;                // 本机 SN
    uint8_t peer_count = 0;     // 匹配节点数量
    peer_info peers[MAX_PEERS]; // 匹配节点数组

    void loadConfigurayion();             // 从 nvs 中读取配置
    int findPeer(const uint8_t *peerMAC); // 检查 MAC 地址 是否在 peers 列表中 -1 : 未找到  -N : 目标 MAC 地址在 peer 中的位置
    int findPeer(uint16_t sn);            // 检查 SN 是否在 peers 列表中 -1 : 未找到  -N : 目标 MAC 地址在 peer 中的位置

    void addPeerToconfig(const uint8_t *mac, uint16_t _sn); // 添加节点
    void addPeerToconfig(peer_info p);                      // 添加节点
    void removePeerToconfig(uint8_t index);                 // 移除节点
    void clearAllPeersOnconfig();                           // 清除所有节点
};
